iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0
AI & Data

30 天入門常見的機器學習演算法系列 第 21

(Day 21) 卷積神經網絡 (Convolutional Neural Network)

  • 分享至 

  • xImage
  •  

在初步暸解全連接神經網絡 (Fully Connected Neural Network) 後,接下來必須介紹的經典架構就是卷積神經網絡 (Convolutional Neural Network; CNN)。卷積神經網絡可以說是深度學習的代表性架構之一,特別是在電腦視覺 (Computer Vision) 領域幾乎無處不在。

在我們的日常生活中,從手機的人臉辨識、影像搜尋、自駕車的影像識別,到醫學影像的腫瘤檢測,都可以看到卷積神經網絡的身影。它能在海量影像資料中自動學習特徵,避免了傳統機器學習需要人工設計特徵的困難。

為什麼需要 CNN?

在前面介紹的 全連接神經網路 (Fully Connected Neural Network, FCNN) 中,我們知道每一層神經元都與上一層的所有輸入相連。但如果我們把輸入換成圖片,就會發現一個很大的問題:

  • 假設輸入是一張大小為 $224 \times 224$ 的彩色圖片,這代表有 $224 \times 224 \times 3 = 150,528$ 個像素值。若將這些像素全部展平成一維向量再輸入 FCNN,第一層神經元若有 1000 個,就需要 1.5 億個參數。這樣的參數量幾乎不可訓練,既浪費計算資源,也容易過擬合。
  • CNN 的設計靈感來自於生物學上對「視覺皮質 (Visual Cortex)」的研究:人類大腦在處理影像時,不是一次性看完整張圖,而是先觀察局部特徵 (邊緣、角落、紋理),再逐步組合成更高層次的語意 (眼睛、鼻子、車輪等)。
  • 因此 CNN 引入了「局部感受野 (Local Receptive Field)」與「權重共享 (Weight Sharing)」兩個關鍵設計,使模型能夠高效處理圖片,同時降低參數數量。

CNN 的基本組件

CNN 主要由以下幾種層組成:

  • 卷積層 (Convolutional Layer)
    • 透過多個卷積核從輸入影像中提取特徵。
    • 每個卷積核產生一張特徵圖 (Feature Map)。
    • 通常會搭配激活函數 (例如 ReLU),引入非線性。
  • 池化層 (Pooling Layer)
    • 池化層的功能是「壓縮資料」並「保留重要特徵」。
    • 常見方法:
      • 最大池化 (Max Pooling): 取區域內的最大值。
      • 平均池化 (Average Pooling):取區域內的平均值。
    • 池化的好處:
      • 降低資料維度,減少參數數量。
      • 增加特徵的不變性 (例如影像的平移或縮放)。
  • 全連接層 (Fully Connected Layer)
    • 通常位於 CNN 的尾端,將高層特徵輸出轉換為分類結果。
    • 與傳統神經網路類似,最後一層常使用 Softmax 作為多分類輸出。

激活函數 (Activation Function)

CNN 常使用 ReLU (Rectified Linear Unit),定義為:

$$
f(x)=max(0,x)
$$

其優點是計算簡單,能有效避免梯度消失問題。

正則化

為了提升模型的泛化能力,常見技術包括:

  • Dropout:隨機丟棄部分神經元,防止過擬合。
  • Batch Normalization:對每層的輸入做標準化,加快收斂速度。

CNN 的運作流程

舉例來說,假設我們要用 CNN 辨識一張手寫數字圖片,流程可能是:

  • 輸入影像 ($28 \times 28$ 像素的灰階圖)。
  • 卷積層 1: 用多個 $3 \times 3$ 卷積核提取邊緣特徵。
  • 池化層 1: 降低解析度,保留主要結構。
  • 卷積層 2: 學習更高階的特徵 (例如圓形、曲線)。
  • 池化層 2: 再度壓縮資料。
  • 全連接層: 將特徵展平,輸入全連接神經網路。
  • 輸出層 (Softmax): 輸出對應於 0–9 的分類機率。

程式實作

給大家看一下就好,跟上一個實作差不多,只是改成 CNN 來預測,讓準確度可以在提升

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# ---------------------------
# 1. 資料預處理與載入
# ---------------------------
transform = transforms.Compose([
    transforms.ToTensor(),  # 轉成 tensor
    transforms.Normalize((0.1307,), (0.3081,))  # 標準化 MNIST 平均值與標準差
])

train_dataset = datasets.MNIST(root="./data", train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root="./data", train=False, transform=transform)

train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)

# ---------------------------
# 2. 定義 CNN 模型
# ---------------------------
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # Conv2d(輸入通道數, 輸出通道數, kernel大小)
        self.conv1 = nn.Conv2d(1, 32, 3, 1)  # (28x28 -> 26x26)
        self.conv2 = nn.Conv2d(32, 64, 3, 1) # (26x26 -> 24x24)
        self.dropout1 = nn.Dropout(0.25)
        self.dropout2 = nn.Dropout(0.5)
        self.fc1 = nn.Linear(9216, 128)  # 64*12*12 = 9216
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)  # (24x24 -> 12x12)
        x = self.dropout1(x)
        x = torch.flatten(x, 1)  # 攤平成一維
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout2(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)  # 分類用 softmax
        return output

model = CNN()

# ---------------------------
# 3. 訓練與測試流程
# ---------------------------
optimizer = optim.Adam(model.parameters(), lr=0.001)

def train(model, device, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data, target
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % 100 == 0:
            print(f"Train Epoch: {epoch} [{batch_idx*len(data)}/{len(train_loader.dataset)}] Loss: {loss.item():.6f}")

def test(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            output = model(data)
            test_loss += F.nll_loss(output, target, reduction='sum').item()
            pred = output.argmax(dim=1, keepdim=True)
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)
    accuracy = 100. * correct / len(test_loader.dataset)
    print(f"Test set: Avg loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({accuracy:.2f}%)\n")

# ---------------------------
# 4. 開始訓練
# ---------------------------
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

for epoch in range(1, 6):  # 訓練 5 個 epoch
    train(model, device, train_loader, optimizer, epoch)
    test(model, device, test_loader)

結論

卷積神經網路之所以能成為深度學習領域的核心技術之一,關鍵在於它善於處理具有空間結構的資料。透過卷積層提取局部特徵、池化層降低維度並保留關鍵資訊,CNN 能有效捕捉圖像、語音等資料中的模式,避免傳統全連接網路因高維度而產生的參數爆炸問題。

以 MNIST 手寫數字辨識為例,CNN 在短短幾個 epoch 內即可達到 98% 以上的準確率,遠優於單純的全連接網路。這顯示 CNN 的結構不僅能提升模型性能,也能在計算效率上取得平衡。
然而,CNN 並非萬能。當資料缺乏明確的局部結構 (例如純結構化數值資料),卷積層的優勢就不明顯;同時,CNN 的運算資源需求相對較高,對硬體 (GPU) 有一定依賴。

總體來說,CNN 已經成為影像辨識與電腦視覺任務的標準解法,後續更衍生出 ResNet、Inception、EfficientNet 等更深更複雜的架構。對初學者而言,理解 CNN 的核心概念 (卷積、池化、權重共享、特徵提取) 是踏入深度學習應用的第一步;對進階研究者而言,則需要進一步思考如何結合 CNN 與其他模型 (如 Transformer) 來應對更複雜的任務。


上一篇
(Day 20) 激活函數 (Activation Function)
下一篇
(Day 22) 深度學習中的正規化與正則化 (Regularization in Deep Learning)
系列文
30 天入門常見的機器學習演算法30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言